package sunyu.kit.redis;

import org.nutz.lang.Lang;
import org.slf4j.Logger;
import redis.clients.jedis.*;
import sunyu.kit.common.ExceptionKit;
import sunyu.kit.common.JsonKit;
import sunyu.kit.common.LogKit;
import sunyu.kit.common.StringKit;

import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Redis工具类
 * <p>
 * Redis命令参考:http://doc.redisfans.com/
 *
 * @author 孙宇
 */
public class RedisKit {

    private static final Logger logger = LogKit.getLogger();

    private static final Map<String, JedisPool> jedisPoolMap = new ConcurrentHashMap<>();//单机的jedis连接池
    private static final Map<String, JedisCluster> jedisClusterMap = new ConcurrentHashMap<>();//集群的jedisCluster连接池

    /**
     * 系统关闭前调用此方法
     */
    public static void destroy() {
        logger.info("销毁jedis和jedisCluster连接池开始");
        for (Map.Entry<String, JedisPool> entry : jedisPoolMap.entrySet()) {
            entry.getValue().destroy();
        }
        for (Map.Entry<String, JedisCluster> entry : jedisClusterMap.entrySet()) {
            try {
                entry.getValue().close();
            } catch (IOException e) {
                logger.error(ExceptionKit.getStackTrace(e));
            }
        }
        logger.info("销毁jedis和jedisCluster连接池结束");
    }

    /**
     * 添加一个jedis链接
     *
     * @param jedisPoolName
     * @param maxTotal
     * @param maxIdle
     * @param maxWaitMillis
     * @param testOnBorrow
     * @param connectionTimeout
     * @param host
     * @param port
     * @param password
     */
    public static void addJedisPool(String jedisPoolName,
                                    Integer maxTotal,
                                    Integer maxIdle,
                                    Integer maxWaitMillis,
                                    Boolean testOnBorrow,
                                    Integer connectionTimeout,
                                    String host,
                                    Integer port,
                                    String password) {
        logger.info("添加一个jedis连接 {}", jedisPoolName);
        if (jedisPoolMap.containsKey(jedisPoolName)) {
            logger.error("jedis连接 {} 已存在!", jedisPoolName);
        } else {
            JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
            jedisPoolConfig.setMaxTotal(maxTotal);
            jedisPoolConfig.setMaxIdle(maxIdle);
            jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
            jedisPoolConfig.setTestOnBorrow(testOnBorrow);
            JedisPool jedisPool = null;
            if (StringKit.isNotBlank(password)) {
                jedisPool = new JedisPool(jedisPoolConfig, host, port, connectionTimeout, password);
            } else {
                jedisPool = new JedisPool(jedisPoolConfig, host, port, connectionTimeout);
            }
            jedisPoolMap.put(jedisPoolName, jedisPool);
        }
    }

    /**
     * 添加一个jedisCluster
     *
     * @param jedisClusterName
     * @param maxTotal
     * @param maxIdle
     * @param maxWaitMillis
     * @param testOnBorrow
     * @param cluster          要符合JSON格式：[{host:'192.168.11.124',port:'7001'},{}...]
     */
    public static void addJedisCluster(String jedisClusterName,
                                       Integer maxTotal,
                                       Integer maxIdle,
                                       Integer maxWaitMillis,
                                       Boolean testOnBorrow,
                                       String cluster) {
        logger.info("添加一个jedis cluster连接 {}", jedisClusterName);
        if (jedisClusterMap.containsKey(jedisClusterName)) {
            logger.error("jedis cluster连接 {} 已存在！", jedisClusterName);
        } else {
            JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
            jedisPoolConfig.setMaxTotal(maxTotal);
            jedisPoolConfig.setMaxIdle(maxIdle);
            jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
            jedisPoolConfig.setTestOnBorrow(testOnBorrow);
            Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();
            List<Map> clusterList = JsonKit.toList(cluster, Map.class);
            if (clusterList != null && clusterList.size() > 0) {
                for (Map<String, String> c : clusterList) {
                    jedisClusterNodes.add(new HostAndPort(c.get("host"), Integer.parseInt(c.get("port"))));
                }
                JedisCluster jedisCluster = new JedisCluster(jedisClusterNodes, jedisPoolConfig);
                jedisClusterMap.put(jedisClusterName, jedisCluster);
            }
        }
    }

    /**
     * 获得一个jedisPool
     *
     * @param jedisPoolName
     * @return
     */
    public static JedisPool getJedisPool(String jedisPoolName) {
        return jedisPoolMap.get(jedisPoolName);
    }

    /**
     * 获得一个jedis
     *
     * @param jedisPoolName
     * @return
     */
    private static Jedis getJedis(String jedisPoolName) {
        return getJedisPool(jedisPoolName).getResource();
    }

    /**
     * 获得一个JedisCluster
     *
     * @param jedisClusterName
     * @return
     */
    public static JedisCluster getJedisCluster(String jedisClusterName) {
        return jedisClusterMap.get(jedisClusterName);
    }

    /**
     * 执行jedis指令
     *
     * @param jedisPoolName
     * @param action
     * @param <T>
     * @return
     */
    public static <T> T execute(String jedisPoolName,
                                JedisCallback<T> action) {
        try (Jedis jedis = getJedis(jedisPoolName)) {
            return action.doInJedis(jedis);
        } catch (Throwable throwable) {
            throw Lang.wrapThrow(throwable);
        }
    }

    /**
     * 由于JedisCluster没有实现keys操作，这里自己实现以下
     *
     * @param pattern
     * @return
     */
    public static Set<String> clusterKeys(String jedisClusterName,
                                          String pattern) {
        Set<String> keys = new HashSet<>();
        Map<String, JedisPool> clusterNodes = getJedisCluster(jedisClusterName).getClusterNodes();
        for (String k : clusterNodes.keySet()) {
            JedisPool jp = clusterNodes.get(k);
            try (Jedis connection = jp.getResource()) {
                keys.addAll(connection.keys(pattern));
            } catch (Exception e) {
                logger.error(ExceptionKit.getStackTrace(e));
            }
        }
        return keys;
    }

    /**
     * 由于JedisCluster没有实现keys操作，这里自己实现以下
     *
     * @param jedisClusterName
     * @param pattern
     * @return
     */
    public static Set<byte[]> clusterKeys(String jedisClusterName,
                                          byte[] pattern) {
        Set<byte[]> keys = new HashSet<>();
        Map<String, JedisPool> clusterNodes = getJedisCluster(jedisClusterName).getClusterNodes();
        for (Object k : clusterNodes.keySet()) {
            JedisPool jp = clusterNodes.get(k);
            try (Jedis connection = jp.getResource()) {
                keys.addAll(connection.keys(pattern));
            } catch (Exception e) {
                logger.error(ExceptionKit.getStackTrace(e));
            }
        }
        return keys;
    }

    /**
     * 由于JedisCluster没有实现FlushDB操作，这里自己实现以下
     *
     * @param jedisClusterName
     */
    public static void clusterFlushDB(String jedisClusterName) {
        Map<String, JedisPool> clusterNodes = getJedisCluster(jedisClusterName).getClusterNodes();
        for (Object k : clusterNodes.keySet()) {
            JedisPool jp = clusterNodes.get(k);
            try (Jedis connection = jp.getResource()) {
                connection.flushDB();
            } catch (Exception e) {
                logger.error(ExceptionKit.getStackTrace(e));
            }
        }
    }

    /**
     * 由于JedisCluster没有实现DbSize操作，这里自己实现以下
     *
     * @param jedisClusterName
     * @return
     */
    public static Long clusterDbSize(String jedisClusterName) {
        Long total = 0L;
        Map<String, JedisPool> clusterNodes = getJedisCluster(jedisClusterName).getClusterNodes();
        for (Object k : clusterNodes.keySet()) {
            JedisPool jp = clusterNodes.get(k);
            try (Jedis connection = jp.getResource()) {
                total += connection.dbSize();
            } catch (Exception e) {
                logger.error(ExceptionKit.getStackTrace(e));
            }
        }
        return total;
    }

}
